package VisibilityGraph;

import java.util.Collections;
import java.util.LinkedList;
import java.util.ListIterator;

import java.util.Hashtable;

import DataModel.CommonLibrary.CGeometricOperations;
import DataModel.LayoutManager.CEdge;
import DataModel.LayoutManager.CRectangle;
import ScanLineAlghorithms.ScanLineFunctions;

/**
 * This class holds and inits hash of neighbouring rectangles.
 * Each entry in hash holds rectangle -  neighbours relationship.
 */
public class NeighboursHash {

	Hashtable<CRectangle,LinkedList<CRectangle>> xDirHash = null;
	Hashtable<CRectangle,LinkedList<CRectangle>> yDirHash = null;
	
	double crossCapAreaX =0;
	double crossCapAreaY =0;
	
	/**
	 * Calculates cross capacitence in X direction
	 * @return total area/distance between neighbours in X direction
	 */
	public double getCrossCapAreaX() {
		return crossCapAreaX;
	}
	/**
	 * Calculates cross capacitence in Y direction
	 * @return total area/distance between neighbours in X direction
	 */
	public double getCrossCapAreaY() {
		return crossCapAreaY;
	}

	/**
	 * Class constructor
	 * @param allRecs - List of rectangles for which we want to create neighbours hash
	 */
	public NeighboursHash(LinkedList<CRectangle> allRecs) {
		init(allRecs);
	}
	
	//Wraper for init function
	private void init(LinkedList<CRectangle> rectangles) {
		xDirHash = new Hashtable<CRectangle,LinkedList<CRectangle>>();
		yDirHash = new Hashtable<CRectangle,LinkedList<CRectangle>>();
		this.neighbourScan(rectangles, 'x');
		this.neighbourScan(rectangles, 'y');
		
	}
	
	
	//This function scans the plane of rectangles in given direction in order to calculate neighboring rectangles
	private void neighbourScan(LinkedList<CRectangle> rectangles , char dir) {
			
		Hashtable<Long, LinkedList<CEdge>> ordinates = null;
		
		if (dir == 'x') {
			ordinates = ScanLineFunctions.initEndpoints(rectangles , 'y');
		} else {
			ordinates = ScanLineFunctions.initEndpoints(rectangles , 'x');
		}
		
		LinkedList<Long> ordEndpoints = new LinkedList<Long>(ordinates.keySet());
		Collections.sort(ordEndpoints);
			
		ListIterator itPoints  = ordEndpoints.listIterator();
		
		long curP2 = ((Long)itPoints.next()).longValue();
		long curP1 = -1;
		while (itPoints.hasNext()) {
			curP1 = curP2;
			curP2 = ((Long)itPoints.next()).longValue();
			
			LinkedList<CRectangle> curRects = null;
	
			Hashtable<Long, LinkedList<CEdge>> scanScale = null;
			if (dir == 'y') {
				curRects = CGeometricOperations.RecsInVerticalArea(curP1, curP2, rectangles);
				scanScale = ScanLineFunctions.initEndpoints(curRects , 'y');
			} else {
				curRects = CGeometricOperations.RecsInHorisontalArea(curP1, curP2, rectangles);
				scanScale = ScanLineFunctions.initEndpoints(curRects , 'x');
			}
			findNeighbours(scanScale,dir,curP1,curP2);
			 
		}
	}
	//Scans the plane in range to calculate neighbouring rectangles
	private void findNeighbours(Hashtable<Long, LinkedList<CEdge>> scanScale, char dir, long curP1, long curP2) {
		LinkedList<Long> keys = new LinkedList<Long>(scanScale.keySet());
		Collections.sort(keys);
		int counterPre =1;
		int counter = 0;
		ListIterator itKeys  = keys.listIterator();
		LinkedList<CEdge> edgesLeft = null;
		LinkedList<CEdge> edgesRight = null;
		
		while (itKeys.hasNext()) {
			Long key = (Long)itKeys.next();
			LinkedList<CEdge> edges = scanScale.get(key);
			if (edges != null) {
				ListIterator itEdges = edges.listIterator();
				while (itEdges.hasNext()) {
					CEdge edge = (CEdge) itEdges.next();
					if (dir == 'x') {
						//rectangle ended
						if (edge.getEndPoint().y<=edge.getStartPoint().y) {
							counter--;
						}
						//rectangle started
						if (edge.getStartPoint().y<=edge.getEndPoint().y) {
							counter++;
						}
					}
					if (dir == 'y') {
						//rectangle ended
						if (edge.getEndPoint().x>=edge.getStartPoint().x) {
							counter--;
						}
						//rectangle started
						if (edge.getStartPoint().x>=edge.getEndPoint().x) {
							counter++;
						}
					}
				}
			}
			//start of free zone
			if (counterPre >0 && counter == 0 ) {
				edgesLeft = edges;
			} 
			//end of free zone
			else if (counterPre == 0 && counter > 0) {
				edgesRight = edges;
				int dist;
				if (dir == 'x') {
					dist= Math.abs(edgesLeft.getFirst().getStartPoint().x - edgesRight.getFirst().getStartPoint().x);
					crossCapAreaX += (double)(Math.abs(curP2-curP1))/(double)(dist);
					
				} else {
					dist= Math.abs(edgesLeft.getFirst().getStartPoint().y - edgesRight.getFirst().getStartPoint().y);
					crossCapAreaY += (double)(Math.abs(curP2-curP1))/(double)(dist);
					
				}
				updateHash(edgesLeft,edgesRight,dir);
				updateHash(edgesRight,edgesLeft,dir);
				edgesLeft =   null;
				edgesRight =  null;	
				
			} 
			counterPre = counter;	
		}
		
	}

	//update the hash with found neighbours
	private void updateHash(LinkedList<CEdge> edgesLeft, LinkedList<CEdge> edgesRight, char dir) {
		for (CEdge eLeft : edgesLeft) {
			for (CEdge eRight : edgesRight) {
				CRectangle recLeft  = eLeft.getOwnerRec();
				CRectangle recRight = eRight.getOwnerRec();
				Hashtable<CRectangle,LinkedList<CRectangle>> curhash = null;
				if (dir == 'x') {
					curhash = this.xDirHash;
				} else {
					curhash = this.yDirHash;
				}
				LinkedList<CRectangle> neighboursList = null;
				if (!curhash.containsKey(recLeft)) {
					neighboursList = new LinkedList<CRectangle>();
					neighboursList.add(recRight);
					curhash.put(recLeft, neighboursList);
				} else {
					neighboursList = curhash.get(recLeft);
					if (!neighboursList.contains(recRight)) neighboursList.add(recRight);
				}
			}
			
		}
		
	}
	/**
	 * This function returns list of neighbours of given rectangle
	 * @param rec - rectangle for which we want to know the neighbours
	 * @param dir - direction of neighbours search , x or y.
	 * @return
	 */
	public LinkedList<CRectangle> getNeighbours(CRectangle rec,char dir) {
		Hashtable<CRectangle,LinkedList<CRectangle>> curhash = null;
		if (dir == 'x') {
			curhash = this.xDirHash;
		} else {
			curhash = this.yDirHash;
		}
		if (!curhash.containsKey(rec)) return null;
		else {
			LinkedList<CRectangle> neighboursList = new LinkedList<CRectangle>(curhash.get(rec));
			return neighboursList;
		}	
	}


}
